NAME

Time::Str - Parse and format date/time strings in multiple standard formats

SYNOPSIS

use Time::Str qw( str2time str2date time2str );

# Parse to Unix timestamp
my $time = str2time('2024-12-24T15:30:45Z');
my $time = str2time('Mon, 24 Dec 2012 15:30:45 +0100', format => 'RFC2822');

# Parse to components
my %date = str2date('2024-12-24T15:30:45.500+01:00');
# (year => 2024, month => 12, day => 24, hour => 15,
#  minute => 30, second => 45, nanosecond => 500000000,
#  tz_offset => 60)

# Format Unix timestamp
my $str = time2str(1735052445);
# '2024-12-24T15:30:45Z'

my $str = time2str(1735052445, format => 'RFC2822', offset => 60);
# 'Tue, 24 Dec 2024 16:30:45 +0100'

DESCRIPTION

Time::Str parses date/time strings from various standard formats into Unix timestamps or components, and formats Unix timestamps back into strings.

Supported standards include ISO 8601, RFC 3339, RFC 2822, RFC 2616 (HTTP), ISO 9075 (SQL), ASN.1, and others. A permissive DateTime parser handles most real-world date/time representations that can be parsed without ambiguity.

FUNCTIONS

str2time

my $time = str2time($string);
my $time = str2time($string, format => $format);
my $time = str2time($string, format => $format, precision => $precision);
my $time = str2time($string, format => 'ASN1UT', pivot_year => 2000);

Parses a date/time string and returns a Unix timestamp (seconds since 1970-01-01T00:00:00Z). The timestamp may include fractional seconds.

The input must include a UTC designator (Z, UTC, GMT) or a numeric timezone offset. Strings with only an unresolved timezone abbreviation (e.g., EST, IST) will croak. See "TIMEZONE ABBREVIATIONS".

Parameters

  • $string (required)

    The date/time string to parse.

  • format (optional, default: 'RFC3339')

    The format specification. See "SUPPORTED FORMATS".

  • precision (optional, default: 6 or 9 depending on float size)

    Number of decimal places to preserve for fractional seconds (0-9). Fractional digits beyond the specified precision are truncated. See "PRECISION HANDLING".

  • pivot_year (optional, default: 1950)

    For formats with two-digit years (ASN1UT, RFC 850 within RFC2616), sets the pivot year for century expansion. Two-digit years less than (pivot_year % 100) map to the next century; others map to the current century.

    With the default pivot of 1950: 49 becomes 2049, 50 becomes 1950, 99 becomes 1999.

Returns

A numeric Unix timestamp, possibly with a fractional part. The supported range is 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z.

Errors

Croaks if:

  • The string cannot be parsed in the specified format

  • Date or time components are out of valid ranges

  • No timezone offset or UTC designator is present

  • A named parameter is unknown or out of range

Examples

# RFC 3339 (default)
my $t = str2time('2024-12-24T15:30:45Z');
# 1735052445

my $t = str2time('2024-12-24T15:30:45.500+01:00');
# 1735048845.5

# RFC 2822
my $t = str2time('Mon, 24 Dec 2012 15:30:45 +0100',
                 format => 'RFC2822');
# HTTP
my $t = str2time('Mon, 24 Dec 2012 15:30:45 GMT',
                 format => 'RFC2616');

# Precision (truncation)
my $t = str2time('2024-12-24T15:30:45.123456789Z', precision => 3);
# 1735052445.123

my $t = str2time('2024-12-24T15:30:45.999999Z', precision => 3);
# 1735052445.999

str2date

my %date = str2date($string);
my %date = str2date($string, format => $format);
my %date = str2date($string, format => 'ASN1UT', pivot_year => 2000);

Parses a date/time string and returns the parsed components. Unlike str2time, this does not require a timezone and preserves all parsed information without converting to a timestamp.

Parameters

  • $string (required)

    The date/time string to parse.

  • format (optional, default: 'RFC3339')

    The format specification. See "SUPPORTED FORMATS".

  • pivot_year (optional, default: 1950)

    For formats with two-digit years, sets the pivot year for century expansion.

Returns

In list context, returns key-value pairs. In scalar context, returns a hash reference.

All values are numeric except tz_utc, tz_abbrev, and tz_annotation. The following components may be present:

  • year - Four-digit year (1-9999)

  • month - Month (1-12)

  • day - Day of month (1-31)

  • hour - Hour in 24-hour format (0-23)

  • minute - Minute (0-59)

  • second - Second (0-60; 60 allows for leap seconds)

  • nanosecond - Fractional seconds as nanoseconds (0-999,999,999)

  • tz_offset - Timezone offset in minutes from UTC (e.g., 60 for +01:00). Present when a numeric offset or UTC designator was parsed.

  • tz_utc - The UTC designator (Z, UTC, GMT) if one was present. When followed by a numeric offset (e.g., UTC+05:30), tz_offset reflects that offset rather than zero.

  • tz_abbrev - The timezone abbreviation as it appeared in the input, if present and not a UTC designator. tz_offset will not be present when tz_abbrev is set. See "TIMEZONE ABBREVIATIONS".

  • tz_annotation - Bracketed timezone tag from the input, if present (RFC 9557 IXDTF or Java ZoneId format, e.g., [Europe/Stockholm]). Informational only; does not affect tz_offset.

Errors

Croaks if:

  • The string cannot be parsed in the specified format

  • Date or time components are out of valid ranges

  • A named parameter is unknown or out of range

Examples

# Full RFC 3339 timestamp
my %d = str2date('2024-12-24T15:30:45.500+01:00');
# (year       => 2024,
#  month      => 12,
#  day        => 24,
#  hour       => 15,
#  minute     => 30,
#  second     => 45,
#  nanosecond => 500000000,
#  tz_offset  => 60)

# Partial dates
my %d = str2date('2024-12-24', format => 'W3CDTF');
# (year => 2024, month => 12, day => 24)

my %d = str2date('2024', format => 'W3CDTF');
# (year => 2024)

# 12-hour clock
my %d = str2date('December 24, 2024, 3:30 PM', format => 'DateTime');
# (..., hour => 15)

# Two-digit year
my %d = str2date('Monday, 24-Dec-50 15:30:45 GMT',
                 format => 'RFC2616');
# (year => 1950, ...)

# Two-digit year with custom pivot
my %d = str2date('Monday, 24-Dec-50 15:30:45 GMT',
                 format => 'RFC2616', pivot_year => 1970);
# (year => 2050, ...)

# UTC designator
my %d = str2date('24 Dec 2012 15:30:45 GMT', format => 'RFC2822');
# (..., tz_utc => 'GMT', tz_offset => 0)

# UTC designator with offset
my %d = str2date('December 24, 2024 at 3:30 pm UTC+05:30',
                 format => 'DateTime');
# (..., tz_utc => 'UTC', tz_offset => 330)

# Timezone abbreviation (unresolved)
my %d = str2date('24 Dec 2012 15:30:45 IST', format => 'RFC2822');
# (..., tz_abbrev => 'IST')

# RFC 9557 annotation
my %d = str2date('2024-12-24T15:30:45.500+01:00[Europe/Stockholm]',
                 format => 'DateTime');
# (..., tz_offset => 60, tz_annotation => '[Europe/Stockholm]')

time2str

my $str = time2str($time);
my $str = time2str($time, format => $format);
my $str = time2str($time, format => $format, offset => $offset);
my $str = time2str($time, nanosecond => $ns, precision => $prec);

Formats a Unix timestamp into a date/time string.

Parameters

  • $time (required)

    Unix timestamp (seconds since 1970-01-01T00:00:00Z). May be an integer or floating-point number. Supported range: 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z.

  • format (optional, default: 'RFC3339')

    The output format. See "SUPPORTED FORMATS". Not all formats support fractional seconds or timezone offsets.

  • offset (optional, default: 0)

    Timezone offset in minutes from UTC. Positive is east, negative is west. Valid range: -1439 to +1439 (-23:59 to +23:59).

  • precision (optional)

    Number of decimal places for fractional seconds (0-9). Fractional seconds are rounded to this precision, which may carry into the seconds field (e.g., rounding .999 at precision 0 yields the next whole second).

    When omitted and the timestamp has fractional seconds, precision is auto-detected: 3 if the fractional part is divisible by 0.001, 6 if divisible by 0.000001, or 9 otherwise. When specified as 0, fractional seconds are omitted after rounding.

    See "PRECISION HANDLING".

  • nanosecond (optional)

    Explicit nanosecond value (0-999,999,999) for fractional seconds. Overrides any fractional part of $time and bypasses rounding. Use this for exact control over fractional output or to preserve nanosecond precision that floating-point cannot represent. Can be combined with precision to control zero-padding.

Returns

A formatted date/time string.

Errors

Croaks if:

  • $time is outside the supported range

  • A named parameter is unknown or out of range

Examples

# RFC 3339 (default)
my $str = time2str(1735052445);
# '2024-12-24T15:30:45Z'

# Timezone offset
my $str = time2str(1735052445, offset => 60);
# '2024-12-24T16:30:45+01:00'

# Fractional seconds (auto-detected precision)
my $str = time2str(1735052445.123456);
# '2024-12-24T15:30:45.123456Z'

# Explicit precision (rounded)
my $str = time2str(1735052445.123456, precision => 3);
# '2024-12-24T15:30:45.123Z'

# Rounding carries into seconds
my $str = time2str(1735052445.999999, precision => 3);
# '2024-12-24T15:30:46.000Z'

# Zero-padding
my $str = time2str(1735052445, precision => 3);
# '2024-12-24T15:30:45.000Z'

# Nanosecond override (no rounding, auto-detected precision)
my $str = time2str(1735052445, nanosecond => 500_000_000);
# '2024-12-24T15:30:45.500Z'

# Zero-padding with nanosecond
my $str = time2str(1735052445, nanosecond => 0, precision => 3);
# '2024-12-24T15:30:45.000Z'

# RFC 2822
my $str = time2str(1735052445, format => 'RFC2822', offset => 60);
# 'Tue, 24 Dec 2024 16:30:45 +0100'

# HTTP (always GMT)
my $str = time2str(1735052445, format => 'RFC2616');
# 'Tue, 24 Dec 2024 15:30:45 GMT'

# SQL with timezone
my $str = time2str(1735052445.5, format => 'ISO9075', offset => 60);
# '2024-12-24 16:30:45.500 +01:00'

# Common Log Format
my $str = time2str(1735052445, format => 'CLF', offset => 60);
# '24/Dec/2024:16:30:45 +0100'

PRECISION HANDLING

The module handles fractional seconds differently when parsing versus formatting.

Parsing (str2time)

Fractional seconds beyond the specified precision are truncated. This preserves the exact digits provided in the input up to the requested precision:

str2time('2024-12-24T15:30:45.123456Z', precision => 3)
# Returns: 1735052445.123 (digits beyond 3rd truncated)

str2time('2024-12-24T15:30:45.999999Z', precision => 3)
# Returns: 1735052445.999 (NOT .000 of next second)

Note: str2date does not accept a precision parameter. It always preserves fractional seconds at full nanosecond resolution in the nanosecond component.

Formatting (time2str)

Fractional seconds are rounded to the specified precision. Rounding prevents floating-point artifacts from appearing in formatted output:

time2str(1735052445.123456, precision => 3)
# '2024-12-24T15:30:45.123Z'

time2str(1735052445.999999, precision => 3)
# '2024-12-24T15:30:46.000Z' (rounds up to next second)

time2str(1735052445.999999, precision => 0)
# '2024-12-24T15:30:46Z' (rounds up, fraction omitted)

Bypassing Rounding

The nanosecond parameter overrides the fractional part of $time and is not subject to rounding, giving exact control over the output:

time2str(1735052445, nanosecond => 999_999_000, precision => 6)
# '2024-12-24T15:30:45.999999Z' (exact, no rounding)

time2str(1735052445, nanosecond => 0)
# '2024-12-24T15:30:45Z' (no fractional part)

FLOATING-POINT PRECISION

Perl's floating-point type (NV) is typically IEEE 754 double-precision (64-bit), providing approximately 15-17 significant decimal digits shared between the integer and fractional parts of a number.

Implications for Timestamps

Whole seconds are always represented exactly within the supported date range (0001-01-01 to 9999-12-31).

Fractional precision depends on the magnitude of the timestamp:

  • Millisecond precision (precision => 3) is exact and stable across the entire supported date range.

  • Microsecond precision (precision => 6) is reliable for timestamps within roughly ±140 years of the Unix epoch (1830-2110). Beyond this range, values may shift by ±1 microsecond due to floating-point spacing.

  • Nanosecond precision cannot be represented faithfully in a floating-point timestamp. Use the nanosecond parameter for exact sub-microsecond values.

Default Precision

When time2str formats a timestamp with fractional seconds and neither precision nor nanosecond is specified, it uses a default based on Perl's NV (floating-point) type:

  • 6 decimal places when NV is double-precision (64-bit)

  • 9 decimal places when NV has extended precision (e.g., long double)

This default prevents spurious trailing digits caused by floating-point representation. For explicit control, always specify precision or nanosecond.

SUPPORTED FORMATS

The following format specifiers are recognized (case-insensitive). The default format is RFC3339.

ANSIC (alias: ctime)

ANSI C asctime() / ctime() format.

DDD MMM (_D|DD) HH:MM:SS YYYY

Where _D is a space-padded single-digit day.

Parsing:

Mon Dec  1 03:04:05 2024
Tue Dec 24 15:30:45 2024

Formatting:

time2str(1735052445, format => 'ANSIC')
# 'Tue Dec 24 15:30:45 2024'

Limitations: Always UTC. Fractional seconds and timezone offsets are not supported; the offset parameter is ignored.

ASN1GT

ASN.1 GeneralizedTime as defined in ITU-T X.680.

YYYYMMDDhh[mm[ss]][(.|,)fraction][Z|±hh[mm]]

Parsing:

Hours are required; minutes and seconds are optional. The fractional part may use a period or comma and applies to the least significant time component present. The timezone designator is optional.

2024122415                   # hour only, no timezone
2024122415Z                  # hour only, UTC
2024122415,5Z                # decimal hour (30 minutes)
201212241530Z                # hour and minute
201212241530,5Z              # decimal minute (30 seconds)
20121224153045               # full time, no timezone
20121224153045Z              # full time, UTC
20121224153045.500Z          # with fractional seconds
20121224153045,123456789Z    # nanoseconds, comma separator
20121224153045+0100          # with numeric offset

Timezone offset format: ±HHMM or ±HH

Formatting:

time2str(1735052445, format => 'ASN1GT')
# '20241224153045Z'

time2str(1735052445, format => 'ASN1GT', precision => 3)
# '20241224153045.500Z'

time2str(1735052445, format => 'ASN1GT', offset => 60)
# '20241224163045+0100'

Limitations: Formatting always outputs hours, minutes, and seconds. Decimal hours and decimal minutes are not produced.

ASN1UT

ASN.1 UTCTime as defined in ITU-T X.680.

YYMMDDhhmm[ss](Z|±hhmm)

Parsing:

Two-digit year; seconds are optional. A timezone designator is required.

9412211010Z           # without seconds
241224153045Z         # with seconds
241224153045+0100     # with numeric offset

Timezone offset format: ±HHMM

Formatting:

time2str(1735052445, format => 'ASN1UT')
# '241224153045Z'

time2str(1735052445, format => 'ASN1UT', offset => 60)
# '241224163045+0100'

Limitations: Fractional seconds are not supported.

CLF

Common Log Format. Used by web servers like Apache and Nginx.

DD/MMM/YYYY:HH:MM:SS[.fraction] ±HHMM

Parsing:

24/Dec/2024:15:30:45 +0100
24/Dec/2024:15:30:45.500 +0100
24/Dec/2024:15:30:45.123456789 -0500

Timezone offset format: ±HHMM

Formatting:

time2str(1735052445, format => 'CLF')
# '24/Dec/2024:15:30:45 +0000'

time2str(1735052445, format => 'CLF', offset => 60)
# '24/Dec/2024:16:30:45 +0100'

time2str(1735052445.5, format => 'CLF', precision => 3, offset => 60)
# '24/Dec/2024:16:30:45.500 +0100'

DateTime

A permissive parser that accepts a wide variety of real-world date/time representations, restricted to those that can be parsed deterministically. Parsing only; cannot be used with time2str.

See "DATETIME FORMAT PARSING" for the full grammar, rules, and examples.

ECMAScript (alias: JavaScript)

ECMAScript Date.prototype.toString format.

DDD MMM DD YYYY HH:MM:SS [GMT|UTC]±HHMM [(comment)]

Parsing:

Day name is required. An optional parenthesized comment (e.g., timezone name) may follow the offset and is discarded. GMT or UTC before the offset is captured in tz_utc.

Tue Dec 24 2024 15:30:45 GMT+0100 (Central European Standard Time)
Tue Dec 24 2024 15:30:45 +0000
Tue Dec 24 2024 15:30:45 UTC+0530 (India Standard Time)
Mon Jan 01 2024 00:00:00 GMT+0000 (Coordinated Universal Time)

Timezone offset format: ±HHMM (required)

Formatting:

time2str(1735052445, format => 'ECMAScript')
# 'Tue Dec 24 2024 15:30:45 GMT+0000'

time2str(1735052445, format => 'ECMAScript', offset => 60)
# 'Tue Dec 24 2024 16:30:45 GMT+0100'

Limitations: Fractional seconds are not supported. Parenthesized comment is not preserved in formatting.

Generic

Deprecated. Use DateTime instead.

GitDate (alias: Git)

Default date format used by Git.

DDD MMM D HH:MM:SS YYYY ±HHMM

Parsing:

Mon Dec 1 03:04:05 2024 +0100
Mon Dec 24 15:30:45 2012 +0100

Timezone offset format: ±HHMM

Formatting:

time2str(1735052445, format => 'GitDate')
# 'Tue Dec 24 15:30:45 2024 +0000'

time2str(1735052445, format => 'GitDate', offset => 60)
# 'Tue Dec 24 16:30:45 2024 +0100'

Limitations: Fractional seconds are not supported.

ISO8601

Calendar date and time as defined in ISO 8601. Accepts both extended (with separators) and basic (compact) forms. Minutes, seconds, fractional seconds, and timezone are all optional when a time is present. The fractional part applies to the least significant time component present, allowing decimal hours and decimal minutes.

Extended format:

YYYY-MM-DD
YYYY-MM-DDThh[:mm[:ss]][(.|,)fraction][Z|±hh[:mm]]

Basic format:

YYYYMMDD
YYYYMMDDThh[mm[ss]][(.|,)fraction][Z|±hh[mm]]

Parsing:

2024-12-24                   # date only (extended)
20241224                     # date only (basic)
2024-12-24T15Z               # hour only
2024-12-24T15,5Z             # decimal hour (15:30)
2024-12-24T15:30Z            # hour and minute
2024-12-24T15:30.5Z          # decimal minute (15:30:30)
2024-12-24T15:30:45Z         # full time
2024-12-24T15:30:45.500Z     # fractional seconds
2024-12-24T15:30:45,500Z     # comma separator
20241224T153045Z             # basic
20241224T1530,5Z             # basic, decimal minute
20241224T153045+0100         # basic with offset
2024-12-24T15:30:45+01:00    # extended with offset

Formatting:

Produces extended format, identical to RFC3339.

time2str(1735052445, format => 'ISO8601')
# '2024-12-24T15:30:45Z'

time2str(1735052445, format => 'ISO8601', offset => 60)
# '2024-12-24T16:30:45+01:00'

time2str(1735052445.5, format => 'ISO8601', precision => 3)
# '2024-12-24T15:30:45.500Z'

Limitations: Formatting always produces extended format with full time (hours, minutes, seconds). Date-only, basic format, decimal hours, and decimal minutes are not produced.

ISO9075 (alias: SQL)

ISO 9075 Database Language SQL timestamp format.

YYYY-MM-DD
YYYY-MM-DD HH:MM:SS[.fraction]
YYYY-MM-DD HH:MM:SS[.fraction] ±HH:MM

Parsing:

2024-12-24
2024-12-24 15:30:45
2024-12-24 15:30:45.500
2024-12-24 15:30:45.123456789
2024-12-24 15:30:45 +01:00
2024-12-24 15:30:45.500 +01:00

Timezone offset format: ±HH:MM

Formatting:

time2str(1735052445, format => 'ISO9075')
# '2024-12-24 15:30:45 +00:00'

time2str(1735052445, format => 'ISO9075', offset => 60)
# '2024-12-24 16:30:45 +01:00'

time2str(1735052445.5, format => 'ISO9075', precision => 3, offset => 60)
# '2024-12-24 16:30:45.500 +01:00'

Limitations: Formatting always includes a timezone offset. Date-only parsing is supported but date-only output is not.

RFC2616 (aliases: RFC7231, HTTP)

HTTP-date as defined in RFC 2616 / RFC 7231. Parses three sub-formats:

DDD, DD MMM YYYY HH:MM:SS GMT    # IMF-fixdate (preferred)
DDDD, DD-MMM-YY HH:MM:SS GMT     # RFC 850 (obsolete)
DDD MMM _D HH:MM:SS YYYY         # ANSI C asctime

Parsing:

Mon, 24 Dec 2012 15:30:45 GMT    # IMF-fixdate
Monday, 24-Dec-12 15:30:45 GMT   # RFC 850
Mon Dec 24 15:30:45 2012         # asctime

Formatting:

Always produces IMF-fixdate in GMT:

time2str(1735052445, format => 'RFC2616')
# 'Tue, 24 Dec 2024 15:30:45 GMT'

Limitations: Always GMT. The offset parameter is ignored.

Fractional seconds are not supported.

RFC2822 (aliases: RFC5322, IMF, EMAIL)

Internet Message Format date as defined in RFC 2822 / RFC 5322.

[DDD,] D MMM YYYY HH:MM[:SS] (±HHMM|UT|UTC|GMT|abbrev)

Parsing:

Day name and seconds are optional. Accepts numeric offsets, UTC designators (UT, UTC, GMT), and timezone abbreviations. Abbreviations are returned in tz_abbrev without resolution.

Mon, 24 Dec 2012 15:30:45 +0100
Mon, 24 Dec 2012 15:30 +0100
24 Dec 2012 15:30:45 +0100
24 Dec 2012 15:30:45 GMT
24 Dec 2012 15:30:45 CET

Timezone offset format: ±HHMM

See "TIMEZONE ABBREVIATIONS".

Formatting:

Always produces a numeric offset:

time2str(1735052445, format => 'RFC2822')
# 'Tue, 24 Dec 2024 15:30:45 +0000'

time2str(1735052445, format => 'RFC2822', offset => 60)
# 'Tue, 24 Dec 2024 16:30:45 +0100'

Limitations: Fractional seconds are not supported.

RFC3339

Internet timestamp as defined in RFC 3339, a profile of ISO 8601.

YYYY-MM-DD(T|t| )HH:MM:SS[.fraction](Z|z|±HH:MM)

Parsing:

The date/time separator may be T, t, or a space. The UTC designator may be Z or z.

2024-12-24T15:30:45Z
2024-12-24t15:30:45z
2024-12-24 15:30:45Z
2024-12-24T15:30:45.500Z
2024-12-24T15:30:45.123456789Z
2024-12-24T15:30:45+01:00
2024-12-24T15:30:45-05:00

Timezone offset format: ±HH:MM

Formatting:

Always produces uppercase T and Z (or ±HH:MM):

time2str(1735052445, format => 'RFC3339')
# '2024-12-24T15:30:45Z'

time2str(1735052445, format => 'RFC3339', offset => 60)
# '2024-12-24T16:30:45+01:00'

time2str(1735052445.5, format => 'RFC3339', precision => 3)
# '2024-12-24T15:30:45.500Z'

RFC3501 (aliases: RFC9051, IMAP)

IMAP internal date format as defined in RFC 3501 and RFC 9051.

DD-MMM-YYYY HH:MM:SS ±HHMM

Parsing:

Day is always two digits (zero-padded). Month name is case-insensitive.

24-Dec-2024 15:30:45 +0100
01-Jan-2024 00:00:00 +0000
24-dec-2024 15:30:45 -0500

Timezone offset format: ±HHMM (required)

Formatting:

time2str(1735052445, format => 'RFC3501')
# '24-Dec-2024 15:30:45 +0000'

time2str(1735052445, format => 'RFC3501', offset => 60)
# '24-Dec-2024 16:30:45 +0100'

Limitations: Fractional seconds are not supported.

RFC4287 (alias: ATOM)

Atom feed timestamp as defined in RFC 4287. Stricter than RFC 3339: requires uppercase T and Z, no space separator, no lowercase designators.

YYYY-MM-DDTHH:MM:SS[.fraction](Z|±HH:MM)

Parsing:

2024-12-24T15:30:45Z
2024-12-24T15:30:45.500Z
2024-12-24T15:30:45.123456789Z
2024-12-24T15:30:45+01:00

Timezone offset format: ±HH:MM

Formatting: Identical to RFC3339.

RFC5280 (alias: x509)

X.509 certificate validity times as defined in RFC 5280. Certificates encode validity as ASN.1 UTCTime (two-digit year) for dates before 2050 and GeneralizedTime (four-digit year) for dates from 2050 onward. Both forms require UTC and do not permit fractional seconds or timezone offsets.

YYMMDDhhmmssZ           # UTCTime
YYYYMMDDhhmmssZ         # GeneralizedTime

Parsing:

Accepts both forms. Two-digit years are expanded using pivot_year (default 1950), which matches the RFC 5280 convention: years 00-49 map to 2000-2049, years 50-99 map to 1950-1999.

121224153045Z            # UTCTime, year 12 -> 2012
491231235959Z            # UTCTime, year 49 -> 2049
500101000000Z            # UTCTime, year 50 -> 1950
20500101000000Z          # GeneralizedTime
99991231235959Z          # GeneralizedTime

Formatting:

Automatically selects the form based on the timestamp: UTCTime for dates before 2050-01-01T00:00:00Z, GeneralizedTime for dates from 2050 onward.

time2str(1356359445, format => 'RFC5280')
# '121224143045Z'        - before 2050, UTCTime

time2str(2524608000, format => 'RFC5280')
# '20500101000000Z'      - from 2050, GeneralizedTime

Limitations: Always UTC. The offset parameter is ignored. Fractional seconds are not supported.

RFC5545 (alias: iCal)

iCalendar date and date-time as defined in RFC 5545.

YYYYMMDD
YYYYMMDDThhmmss[Z]

Parsing:

Date-only and date-time forms are accepted. Without Z, parses as local time with no tz_offset.

20241224                     # date only
20241224T153045              # local time
20241224T153045Z             # UTC

Formatting:

time2str(1735052445, format => 'RFC5545')
# '20241224T153045Z'

Limitations: Always UTC. The offset parameter is ignored. Fractional seconds are not supported.

RFC9557 (alias: IXDTF)

Internet Extended Date/Time Format as defined in RFC 9557. Extends RFC 3339 with optional bracketed suffix tags for timezone annotations and calendar systems.

YYYY-MM-DD(T|t| )HH:MM:SS[.fraction](Z|z|±HH:MM)[tags]

Parsing:

The base timestamp follows RFC 3339 syntax. One or more bracketed suffix tags may follow the timezone. Tag content matches [0-9A-Za-z!+-._/]+. Tags are captured verbatim in tz_annotation without validation or interpretation; no constraints are enforced on tag content beyond the character class. Intended for use with str2date.

2024-12-24T15:30:45Z
2024-12-24T15:30:45+01:00[Europe/Stockholm]
2024-12-24T15:30:45Z[Europe/Stockholm]
2024-12-24T15:30:45.500+05:30[Asia/Kolkata]
2024-12-24T15:30:45Z[Europe/Stockholm][u-ca=gregory]

Timezone offset format: ±HH:MM

Formatting: Identical to RFC3339 (tags are not preserved).

RubyDate (alias: Ruby)

Date format popularized by Ruby on Rails and Twitter.

DDD MMM DD HH:MM:SS ±HHMM YYYY

Parsing:

Mon Dec 01 03:04:05 +0100 2024
Mon Dec 24 15:30:45 +0100 2012

Timezone offset format: ±HHMM

Formatting:

time2str(1735052445, format => 'RubyDate')
# 'Tue Dec 24 15:30:45 +0000 2024'

time2str(1735052445, format => 'RubyDate', offset => 60)
# 'Tue Dec 24 16:30:45 +0100 2024'

Limitations: Fractional seconds are not supported.

UnixDate (alias: Unix)

The date(1) command output format as defined by POSIX.

DDD MMM (_D|DD) HH:MM:SS (±HHMM|UTC|GMT|abbrev) YYYY
DDD MMM (_D|DD) HH:MM:SS YYYY (±HHMM|UTC|GMT|abbrev)

Where _D is a space-padded single-digit day.

Parsing:

Accepts the timezone before or after the year. Accepts numeric offsets, UTC designators (UTC, GMT), and timezone abbreviations. Abbreviations are returned in tz_abbrev without resolution.

Mon Dec  1 03:04:05 2024 UTC
Mon Dec 24 15:30:45 2012 UTC
Mon Dec  1 03:04:05 UTC 2024
Mon Dec 24 15:30:45 UTC 2012

Timezone offset format: ±HHMM

See "TIMEZONE ABBREVIATIONS".

Formatting:

Always produces the zone-before-year. Uses UTC for zero offset and a numeric offset otherwise.

time2str(1735052445, format => 'UnixDate')
# 'Tue Dec 24 15:30:45 UTC 2024'

time2str(1735052445, format => 'UnixDate', offset => 60)
# 'Tue Dec 24 16:30:45 +0100 2024'

Limitations: Fractional seconds are not supported.

UnixStamp

Unix date(1) based format with optional fractional seconds and timezone.

[DDD ] MMM (_D|D|DD) hh:mm[:ss[.fraction]] [±HHMM|UTC|GMT|abbrev] YYYY
[DDD ] MMM (_D|D|DD) hh:mm[:ss[.fraction]] YYYY [±HHMM|UTC|GMT|abbrev]

Parsing:

Day name is optional. Day may be space-padded or zero-padded. Seconds and fractional seconds are optional. Timezone may appear before or after the year.

Sun Jul 17 06:53:23 2022
Sun Jul  3 06:53:23 2022
Jul 17 06:53:23 2022
Sun Jul 17 20:35:45.089105460 2022
Sun Jul 17 06:53:23 +0100 2022
Sun Jul 17 06:53:23 EDT 2022
Sun Jul 17 06:53:23 2022 +0100
Sun Jul 17 06:53:23 2022 EDT
Sun Jul 17 06:53:23 GMT+0100 2022
Jul 17 06:53 2022

Timezone offset format: ±HHMM

See "TIMEZONE ABBREVIATIONS".

Formatting:

Always produces the zone-before-year. Uses UTC for zero offset and a numeric offset otherwise.

time2str(1658044403, format => 'UnixStamp')
# 'Sun Jul 17 06:53:23 UTC 2022'

time2str(1658044403, format => 'UnixStamp', offset => 60)
# 'Sun Jul 17 07:53:23 +0100 2022'

time2str(1658044403.5, format => 'UnixStamp', precision => 3)
# 'Sun Jul 17 06:53:23.500 UTC 2022'

W3CDTF (alias: W3C)

W3C Date and Time Format, a profile of ISO 8601.

YYYY
YYYY-MM
YYYY-MM-DD
YYYY-MM-DDTHH:MM:SS[.fraction](Z|±HH:MM)

Parsing:

Supports partial dates. A timezone designator is required when a time component is present.

2024                             # year only
2024-12                          # year and month
2024-12-24                       # date only
2024-12-24T15:30:45Z             # full datetime
2024-12-24T15:30:45.500Z
2024-12-24T15:30:45.123456789Z
2024-12-24T15:30:45+01:00

Timezone offset format: ±HH:MM

Formatting: Identical to RFC3339.

DATETIME FORMAT PARSING

The DateTime format parser accepts a wide variety of real-world date/time representations, restricted to those that can be parsed deterministically. A date is always required. Time and timezone are optional, but a timezone may only appear when a time is present.

Date

A date is always required. Three categories are accepted.

Numeric Dates (Y-M-D only)

When all three components are numeric, they must appear in year-month-day order to avoid the ambiguity between American (M/D/Y) and European (D/M/Y) conventions. The separator must be a hyphen, slash, or period, consistent within the date.

2024-12-24
2024/12/24
2024.12.24

These are rejected:

12-24-2024  # M-D-Y not accepted
24-12-2024  # D-M-Y not accepted
2024-12/24  # mixed separators

Dates with Textual Months

When the month is given as a name or Roman numeral it is unambiguous, so the day and year may appear in any order relative to the month. Month names may be abbreviated (Jan, Feb, ...) or written in full (January, February, ...).

Roman numerals (I through XII) are accepted only in day-month-year order.

Month names and Roman numerals are all case-insensitive.

Separator-based (hyphen, slash, or period, consistent within the date):

2024-Dec-24       # Y-M-D
24-Dec-2024       # D-M-Y
Dec-24-2024       # M-D-Y
2024/December/24
24.XII.2024       # Roman numeral, D-M-Y only

Space-separated:

24 December 2024
December 24, 2024
24. XII. 2024

In space-separated dates, an ordinal suffix (st, nd, rd, th) or a trailing period may follow the day. A trailing period or comma may follow the month:

24th December 2024
24. December 2024
December 24th, 2024
24. XII. 2024
24 XII. 2024
24. XII 2024

Compact Dates (No Separators)

Accepted only when the month is textual. Roman numerals are accepted in day-month-year order:

24DEC2024
24Dec2024
2024DEC24
2024Dec24
24XII2024

Day Names

An optional day name prefix (e.g., Monday, or Mon,) is accepted before the date but not validated against the actual date. Day names are case-insensitive.

Monday, 24 December 2024
Mon, 24 Dec 2024

Year

A four-digit year is always required. Two-digit years are not accepted.

Time of Day

Optional. When present, it must be separated from the date by T, a comma and space, the word at surrounded by spaces, or a plain space.

2024-12-24T15:30:45
2024-12-24 15:30:45
December 24, 2024, 15:30
Monday, 24th December 2024 at 3:30 pm

Either HH:MM or HH with an AM/PM indicator is required as a minimum.

24-Hour Time

Hours and minutes separated by a colon. Hours may be one or two digits; minutes are always two digits. Seconds are optional (two digits, colon-separated). An optional fractional part may follow the seconds, separated by a period or comma, up to nine digits.

15:30
9:05
15:30:45
15:30:45.500
15:30:45,500
15:30:45.123456789

12-Hour Time

An AM/PM indicator is required. Hours must be 1-12. Minutes, seconds, and fractional seconds follow the same rules as 24-hour time but are optional. The indicator may appear with or without a separating space.

Accepted forms: AM, am, PM, pm, A.M., a.m., P.M., p.m.

3 PM
3PM
3:30 PM
3:30:45 pm
3:30:45.500 P.M.
12 p.m.

Midnight is 12:00 AM, noon is 12:00 PM.

Timezone

Optional; may only appear when a time is present.

UTC Designators

Z, UTC, and GMT set tz_utc to the matched designator and tz_offset to 0, unless followed by a numeric offset (e.g., UTC+05:30), in which case tz_offset reflects that offset.

Numeric Offsets

Two-digit hours only:

±HH
±HHMM
±HH:MM

+01
+0100
+01:00
+05:30

Combined UTC and Offset

UTC or GMT followed immediately by a numeric offset. Single-digit hours are only accepted in this combined form. tz_utc is set to the designator; tz_offset reflects the numeric part:

UTC+1
UTC+5:30
UTC+01:00
GMT+5:30

Abbreviations

Abbreviations matching [A-Z][A-Za-z][A-Z]{1,4} are accepted. UTC designators are handled as above; all others (e.g., EST, CET, IST) are returned in tz_abbrev without resolution.

See "TIMEZONE ABBREVIATIONS".

Annotations

RFC 9557 (IXDTF) bracketed tags and Java ZoneId annotations are captured in tz_annotation. Parenthesized comments are accepted but discarded.

+01:00[Europe/Stockholm]
+01:00[u-ca=hebrew]
+0100[Europe/Stockholm]
+0100 (CET)
+0100 (Central European Time)

Validation

  • Dates must be valid for the Gregorian calendar, including leap years

  • Hours: 0-23 (24-hour) or 1-12 (12-hour with AM/PM)

  • Minutes: 0-59

  • Seconds: 0-60 (60 allows for leap seconds)

Examples

# ISO 8601
2024-12-24
2024-12-24T15:30
2024-12-24T15:30+01
2024-12-24T15:30:45,500+01

# RFC 3339
2024-12-24T15:30:45+01:00
2024-12-24T15:30:45.500+01:00

# RFC 9557
2024-12-24T15:30:45.500+01:00[Europe/Stockholm]

# RFC 2822
Mon, 24 Dec 2024 15:30:45 +0100
24 Dec 2024 15:30 +0100

# RFC 2616 (HTTP)
Mon, 24 Dec 2024 15:30:45 GMT

# RFC 3501 (IMAP)
24-Dec-2024 15:30:45 +0100

# ISO 9075 (SQL)
2024-12-24 15:30:45
2024-12-24 15:30:45.500 +01:00

# ECMAScript Date.prototype.toString
Mon Dec 24 2024 15:30:45 GMT+0100 (Central European Time)

# Long-form textual
Monday, 24 December 2024, 15:30 GMT+1
Monday, 24th December 2024 at 3:30 pm UTC+1
December 24th, 2024 at 3:30 PM

# Short-form variations
Dec/24/2024 03:30:45 PM
24. XII. 2024 12PM UTC+1
24DEC2024 12:30:45.500
24.Dec.2024 15:30:45

TIMEZONE ABBREVIATIONS

The DateTime, RFC2822, and Unix parsers accept timezone abbreviations.

  • UTC designators (UTC, GMT, UT, Z)

    Unambiguous. Set tz_utc to the matched designator and tz_offset to 0, unless followed by a numeric offset.

  • All other abbreviations

    Returned as-is in tz_abbrev. tz_offset will not be present. This module intentionally does not resolve abbreviations like EST, CET, or IST to numeric offsets, as many are ambiguous (e.g., IST could mean India Standard Time UTC+5:30, Israel Standard Time UTC+2, or Irish Standard Time UTC+1).

str2time requires a resolved offset and will croak if only an unresolved abbreviation is present. Use str2date to retrieve the components including tz_abbrev, then resolve externally:

my %d = str2date('24 Dec 2012 15:30:45 IST', format => 'RFC2822');
# (..., tz_abbrev => 'IST')

STANDARDS

RATIONALE

strftime and strptime

strftime and strptime are the traditional C library interfaces for formatting and parsing date/time strings. While widely available, they have limitations that make them unsuitable for reliable timestamp handling across formats:

  • Both strftime and strptime are subject to the current locale. Month and day names vary by locale, making them unreliable for formats that require English names (e.g., RFC 2822, RFC 2616). Even English locales are not consistent - abbreviated month names may differ (e.g., Sep vs Sept). Using these functions for standard formats requires the C locale; altering the locale in a running process has issues with thread safety, side effects, and slow performance.

  • strptime requires a fixed format string and cannot express optional components (e.g., optional day name in RFC 2822, optional seconds in ISO 8601). Handling variations requires multiple strptime calls or manual pre-processing.

  • strptime cannot parse ISO 8601 basic format date-times (e.g., 20241224T153045Z) where date and time components lack separators.

  • strftime cannot produce ISO 8601 extended timezone offsets (±HH:MM) portably. The %z conversion produces basic format (±HHMM) and there is no standard conversion for the extended form.

  • Neither interface handles fractional seconds or nanosecond precision.

  • There is no built-in validation of parsed components - out-of-range values such as month 13, day 32, or hour 25 are silently accepted.

Time::Str provides named format specifiers that encapsulate the parsing and formatting rules for each standard, with strict validation of all components.

Numeric Date Ambiguity

Numeric dates in year-month-day order (YYYY-MM-DD) are unambiguous. However, when the year is not the leading component, the interpretation depends on convention: 01/02/2024 is January 2nd in American format (MM/DD/YYYY) but February 1st in European format (DD/MM/YYYY).

When both the first and second components are in the range 1-12, either interpretation produces a valid date - 144 such combinations exist (12 months x 12 days), making it impossible to determine the intended format from the string alone.

Existing modules handle this differently:

Date::Parse

Assumes American (MM/DD). Swaps to year-month-day only if the first value exceeds 12. Documents this as a known bug with no workaround.

Date::Parse::Modern

Assumes European (DD/MM/YYYY) for non-year-leading formats. Swaps day and month if the month value exceeds 12.

Time::ParseDate

Assumes American (MM/DD) by default. A UK option switches to European (DD/MM). Applies heuristics when values exceed 12.

Time::Str avoids this problem entirely: numeric dates must be in year-month-day order. Ambiguous formats are rejected. When the month is given as a name or Roman numeral, day and year may appear in any order since the month is unambiguous.

Two-Digit Year Ambiguity

A two-digit year introduces century ambiguity: 01/02/03 could be 2003-01-02, 2003-02-01, or 2001-02-03 depending on which component is assumed to be the year and which convention is used for month and day. Existing modules apply different heuristics:

Date::Parse

Assumes the year is the last component (MM/DD/YY). Expands two-digit years using a fixed split: 69-99 to 1969-1999, 0-68 to 2000-2068.

Date::Parse::Modern

Requires a four-digit year in numeric dates. Two-digit year dates such as 01/02/03 are not recognised. When a two-digit year appears with a textual month (e.g., 17 March 94), it is unconditionally mapped to the 1900s.

Time::ParseDate

Infers the year position from value ranges. When all three values are 12 or below, defaults to MM/DD/YY. Expands two-digit years relative to the current year with PREFER_PAST and PREFER_FUTURE options.

The same input produces different results depending on the module and, in some cases, the current date.

The datetime format in Time::Str requires a four-digit year, avoiding century ambiguity entirely. Formats that inherently use two-digit years (ASN1UT, RFC 850 within RFC2616) provide a configurable pivot_year parameter with a documented default.

Ambiguous Timezone Abbreviations

Timezone abbreviations such as EST, IST, and CST are inherently ambiguous. IST may refer to India Standard Time (+05:30), Israel Standard Time (+02:00), or Irish Standard Time (+01:00). CST may refer to US Central Standard Time (-06:00), China Standard Time (+08:00), or Cuba Standard Time (-05:00).

There is no authoritative registry of abbreviations, and existing modules resolve them differently: Date::Parse, Date::Parse::Modern, and Time::ParseDate each ship their own lookup tables with conflicting mappings.

Even when a mapping is correct for the present, it may not be historically accurate. Timezone rules change: regions adopt new offsets, daylight saving rules are revised or abolished, and political decisions alter zone boundaries. A static lookup table cannot account for these changes across time.

Time::Str captures abbreviations in tz_abbrev without attempting to resolve them, leaving interpretation to the caller.

DIAGNOSTICS

Usage: %s

(F) A function was called with an incorrect number of arguments.

Parameter 'format' is unknown: '%s'

(F) The format specifier is not recognised.

Parameter '%s' is out of range

(F) A parameter value is outside its valid range.

Unknown named parameter: '%s'

(F) An unrecognised named parameter was given.

Unable to parse: %s

(F) str2date or str2time could not parse the input string.

Unable to convert: timestamp string without a UTC designator or numeric offset

(F) str2time successfully parsed the input string but cannot convert it to a Unix timestamp without a UTC designator or numeric offset.

LIMITATIONS

  • No support for locale-specific formatting or parsing. Month and day names are English only.

  • No support for ISO 8601 week-dates (e.g., 2024-W52-2) or ordinal dates (e.g., 2024-359).

  • Timestamps are limited to the year range 0001-9999. Most string representation formats cannot represent years outside this range.

  • Timezone abbreviations (e.g., EST, IST) are captured in tz_abbrev but are not resolved to UTC offsets. str2time requires a UTC designator or numeric offset and will croak if only an abbreviation is present.

EXPORTS

Nothing is exported by default. Functions can be imported individually or by category:

use Time::Str qw(time2str str2time str2date);  # individual functions
use Time::Str qw(:all);                        # all functions

SEE ALSO

Time::Moment, DateTime, Time::Piece

AUTHOR

Christian Hansen

COPYRIGHT AND LICENSE

Copyright (C) 2026 by Christian Hansen

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.