001/*
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2026, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v2.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.core.rolling;
015
016import static ch.qos.logback.core.CoreConstants.CODES_URL;
017
018import java.io.File;
019import java.time.Instant;
020import java.time.ZoneId;
021import java.util.Locale;
022import java.util.TimeZone;
023import java.util.concurrent.atomic.AtomicLong;
024
025import ch.qos.logback.core.CoreConstants;
026import ch.qos.logback.core.rolling.helper.ArchiveRemover;
027import ch.qos.logback.core.rolling.helper.DateTokenConverter;
028import ch.qos.logback.core.rolling.helper.RollingCalendar;
029import ch.qos.logback.core.spi.ContextAwareBase;
030
031/**
032 * Base implementation of {@link TimeBasedFileNamingAndTriggeringPolicy}.
033 *
034 * <p>See also derived classes {@link DefaultTimeBasedFileNamingAndTriggeringPolicy} and
035 * {@link SizeAndTimeBasedFileNamingAndTriggeringPolicy}.
036 *
037 * </p>
038 *
039 * @param <E>
040 */
041abstract public class TimeBasedFileNamingAndTriggeringPolicyBase<E> extends ContextAwareBase
042        implements TimeBasedFileNamingAndTriggeringPolicy<E> {
043
044    static private String COLLIDING_DATE_FORMAT_URL = CODES_URL + "#rfa_collision_in_dateFormat";
045
046    protected TimeBasedRollingPolicy<E> enclosingTBRP;
047
048    protected ArchiveRemover archiveRemover = null;
049    protected String elapsedPeriodsFileName;
050    protected RollingCalendar rc;
051
052    protected long artificialCurrentTime = -1;
053
054    protected AtomicLong atomicNextCheck = new AtomicLong(0);
055    protected Instant dateInCurrentPeriod = null;
056
057    protected boolean started = false;
058    protected boolean errorFree = true;
059
060    protected ZoneId zoneId = ZoneId.systemDefault();
061
062    public boolean isStarted() {
063        return started;
064    }
065
066    public void start() {
067        DateTokenConverter<Object> dtc = enclosingTBRP.fileNamePattern.getPrimaryDateTokenConverter();
068        if (dtc == null) {
069            throw new IllegalStateException(
070                    "FileNamePattern [" + enclosingTBRP.fileNamePattern.getPattern() + "] does not contain a valid DateToken");
071        }
072
073        if (dtc.getZoneId() != null) {
074            this.zoneId = dtc.getZoneId();
075            TimeZone tz = TimeZone.getTimeZone(zoneId);
076            rc = new RollingCalendar(dtc.getDatePattern(), tz, Locale.getDefault());
077        } else {
078            rc = new RollingCalendar(dtc.getDatePattern());
079        }
080        addInfo("The date pattern is '" + dtc.getDatePattern() + "' from file name pattern '"
081                + enclosingTBRP.fileNamePattern.getPattern() + "'.");
082        rc.printPeriodicity(this);
083
084        if (!rc.isCollisionFree()) {
085            addError(
086                    "The date format in FileNamePattern will result in collisions in the names of archived log files.");
087            addError(CoreConstants.MORE_INFO_PREFIX + COLLIDING_DATE_FORMAT_URL);
088            withErrors();
089            return;
090        }
091
092        long timestamp = getCurrentTime();
093        setDateInCurrentPeriod(timestamp);
094
095        if (enclosingTBRP.getParentsRawFileProperty() != null) {
096            File currentFile = new File(enclosingTBRP.getParentsRawFileProperty());
097            if (currentFile.canRead()) {
098                long lastModified = currentFile.lastModified();
099                if(lastModified < timestamp) {
100                    timestamp = lastModified;
101                } else {
102                    addWarn("Current time is earlier than last modified of file [" + currentFile.getAbsolutePath() + "]. Ignoring last modified and using current time.");
103                }
104                setDateInCurrentPeriod(timestamp);
105            }
106        }
107        addInfo("Setting initial period to " + dateInCurrentPeriod);
108        long nextCheck = computeNextCheck(timestamp);
109        atomicNextCheck.set(nextCheck);
110    }
111
112    public void stop() {
113        started = false;
114    }
115
116    protected long computeNextCheck(long timestamp) {
117        return rc.getNextTriggeringDate(Instant.ofEpochMilli(timestamp)).toEpochMilli();
118    }
119
120    public String getElapsedPeriodsFileName() {
121        return elapsedPeriodsFileName;
122    }
123
124    public String getCurrentPeriodsFileNameWithoutCompressionSuffix() {
125        return enclosingTBRP.fileNamePatternWithoutCompSuffix.convert(dateInCurrentPeriod);
126    }
127
128    protected void setDateInCurrentPeriod(long timestamp) {
129        dateInCurrentPeriod = Instant.ofEpochMilli(timestamp);
130    }
131
132    public void setCurrentTime(long timeInMillis) {
133        artificialCurrentTime = timeInMillis;
134    }
135
136    public long getCurrentTime() {
137        // if time is forced return the time set by user
138        if (artificialCurrentTime >= 0) {
139            return artificialCurrentTime;
140        } else {
141            return System.currentTimeMillis();
142        }
143    }
144
145    public void setTimeBasedRollingPolicy(TimeBasedRollingPolicy<E> _tbrp) {
146        this.enclosingTBRP = _tbrp;
147
148    }
149
150    public ArchiveRemover getArchiveRemover() {
151        return archiveRemover;
152    }
153
154    protected void withErrors() {
155        errorFree = false;
156    }
157
158    protected boolean isErrorFree() {
159        return errorFree;
160    }
161
162}