Home Reference Source

packages/locale/src/format/parser/LocalizedOffsetPrinterParser.js

/**
 * @copyright (c) 2016, Philipp Thürwächter & Pattrick Hüper
 * @copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
 * @license BSD-3-Clause (see LICENSE in the root directory of this source tree)
 */

import {
    _ as jodaInternal,
    DateTimeFormatterBuilder,
    ChronoField,
    TextStyle
} from '@js-joda/core';

const { MathUtil } = jodaInternal;
//-----------------------------------------------------------------------
/**
 * Prints or parses field text.
 * @private
 */
export default class LocalizedOffsetPrinterParser {
    /**
     * Constructor.
     *
     * @param {!TextStyle} textStyle  the text style, not null
     */
    constructor(textStyle) {
        this._textStyle = textStyle;
    }

    textStyle() {
        return this._textStyle;
    }

    print(context, buf) {
        const offsetSecs = context.getValue(ChronoField.OFFSET_SECONDS);
        /* istanbul ignore if */ // shouldn't happen... getValue throws for unsupported fields?
        if (offsetSecs == null) {
            return false;
        }
        buf.append('GMT');
        if (this._textStyle === TextStyle.FULL) {
            return new DateTimeFormatterBuilder.OffsetIdPrinterParser('', '+HH:MM:ss').print(context, buf);
        }
        const totalSecs = MathUtil.safeToInt(offsetSecs);
        if (totalSecs !== 0) {
            const absHours = Math.abs(MathUtil.intMod(MathUtil.intDiv(totalSecs, 3600), 100));  // anything larger than 99 silently dropped
            const absMinutes = Math.abs(MathUtil.intMod(MathUtil.intDiv(totalSecs,60), 60));
            const absSeconds = Math.abs(MathUtil.intMod(totalSecs, 60));
            buf.append(totalSecs < 0 ? '-' : '+').append(absHours);
            if (absMinutes > 0 || absSeconds > 0) {
                buf.append(':')
                    .append(MathUtil.intDiv(absMinutes, 10)).append(MathUtil.intMod(absMinutes,10));
                if (absSeconds > 0) {
                    buf.append(':')
                        .append(MathUtil.intDiv(absSeconds, 10)).append(MathUtil.intMod(absSeconds, 10));
                }
            }
        }
        return true;
    }

    parse(context, text, position) {
        if (context.subSequenceEquals(text, position, 'GMT', 0, 3) === false) {
            return ~position;
        }
        position += 3;
        if (this._textStyle === TextStyle.FULL) {
            return new DateTimeFormatterBuilder.OffsetIdPrinterParser('', '+HH:MM:ss').parse(context, text, position);
        }
        const end = text.length;
        if (position === end) {
            return context.setParsedField(ChronoField.OFFSET_SECONDS, 0, position, position);
        }
        const sign = text.charAt(position);
        if (sign !== '+' && sign !== '-') {
            return context.setParsedField(ChronoField.OFFSET_SECONDS, 0, position, position);
        }
        const negative = (sign === '-' ? -1 : 1);
        /* istanbul ignore if */ // cannot happen, already checked before
        if (position === end) {
            return ~position;
        }
        position++;
        // hour
        let ch = text.charAt(position);
        if (ch < '0' || ch > '9') {
            return ~position;
        }
        position++;
        let hour = MathUtil.parseInt(ch);
        if (position !== end) {
            ch = text.charAt(position);
            if (ch >= '0' && ch <= '9') {
                hour = hour * 10 + MathUtil.parseInt(ch);
                if (hour > 23) {
                    return ~position;
                }
                position++;
            }
        }
        if (position === end || text.charAt(position) !== ':') {
            const offset = negative * 3600 * hour;
            return context.setParsedField(ChronoField.OFFSET_SECONDS, offset, position, position);
        }
        position++;
        // minute
        if (position > end - 2) {
            return ~position;
        }
        ch = text.charAt(position);
        if (ch < '0' || ch > '9') {
            return ~position;
        }
        position++;
        let min = MathUtil.parseInt(ch);
        ch = text.charAt(position);
        if (ch < '0' || ch > '9') {
            return ~position;
        }
        position++;
        min = min * 10 + MathUtil.parseInt(ch);
        if (min > 59) {
            return ~position;
        }
        if (position === end || text.charAt(position) !== ':') {
            const offset = negative * (3600 * hour + 60 * min);
            return context.setParsedField(ChronoField.OFFSET_SECONDS, offset, position, position);
        }
        position++;
        // second
        if (position > end - 2) {
            return ~position;
        }
        ch = text.charAt(position);
        if (ch < '0' || ch > '9') {
            return ~position;
        }
        position++;
        let sec = MathUtil.parseInt(ch);
        ch = text.charAt(position);
        if (ch < '0' || ch > '9') {
            return ~position;
        }
        position++;
        sec = sec * 10 + MathUtil.parseInt(ch);
        if (sec > 59) {
            return ~position;
        }
        const offset = negative * (3600 * hour + 60 * min + sec);
        return context.setParsedField(ChronoField.OFFSET_SECONDS, offset, position, position);
    }

    toString() {
        return `LocalizedOffset(${this._textStyle})`;
    }

}

//-----------------------------------------------------------------------