001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2015, 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 v1.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.util;
015
016import java.time.Instant;
017import java.time.OffsetDateTime;
018import java.time.ZoneId;
019import java.time.format.DateTimeFormatter;
020import java.util.Locale;
021import java.util.concurrent.atomic.AtomicReference;
022
023/**
024 * A CAS implementation of DateTimeFormatter (previously SimpleDateFormat) which
025 * caches results for the duration of a millisecond.
026 * 
027 * @author Ceki Gülcü
028 * @since 0.9.29
029 */
030public class CachingDateFormatter {
031
032    final DateTimeFormatter dtf;
033    final ZoneId zoneId;
034    final AtomicReference<CacheTuple> atomicReference;
035
036    static class CacheTuple {
037        final long lastTimestamp;
038        final String cachedStr;
039
040        public CacheTuple(long lastTimestamp, String cachedStr) {
041            super();
042            this.lastTimestamp = lastTimestamp;
043            this.cachedStr = cachedStr;
044        }
045    }
046
047    public CachingDateFormatter(String pattern) {
048        this(pattern, null);
049    }
050
051    public CachingDateFormatter(String pattern, ZoneId aZoneId) {
052        this(pattern, aZoneId, null);
053    }
054
055    public CachingDateFormatter(String pattern, ZoneId aZoneId, Locale aLocale) {
056        if (aZoneId == null) {
057            this.zoneId = ZoneId.systemDefault();
058        } else {
059            this.zoneId = aZoneId;
060        }
061        Locale locale = aLocale != null ? aLocale : Locale.getDefault();
062
063        dtf = DateTimeFormatter.ofPattern(pattern).withZone(this.zoneId).withLocale(locale);
064        CacheTuple cacheTuple = new CacheTuple(-1, null);
065        this.atomicReference = new AtomicReference<>(cacheTuple);
066    }
067
068    public final String format(long now) {
069        CacheTuple localCacheTuple = atomicReference.get();
070        CacheTuple oldCacheTuple = localCacheTuple;
071
072        if (now != localCacheTuple.lastTimestamp) {
073            Instant instant = Instant.ofEpochMilli(now);
074            String result = dtf.format(instant);
075            localCacheTuple = new CacheTuple(now, result);
076            // allow a single thread to update the cache reference
077            atomicReference.compareAndSet(oldCacheTuple, localCacheTuple);
078        }
079        return localCacheTuple.cachedStr;
080    }
081
082}