or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

authentication.mdcontent-processing.mdcontent-types.mdcookies.mddate-utilities.mdheader-parsing.mdheaders.mdhttp-message-extensions.mdhttp-methods-status.mdindex.mdmultipart.mdparameters.mdurl-encoding.mdurl-handling.md
tile.json

date-utilities.mddocs/

Date Utilities

HTTP and cookie date parsing and formatting utilities providing conversion between strings and GMT dates with support for multiple HTTP date formats and RFC6265 cookie date format.

Capabilities

HTTP Date Parsing

Parse HTTP date strings into GMT date objects supporting multiple standard HTTP date formats.

/**
 * Parse HTTP date string into GMTDate
 * Supports multiple HTTP date formats including RFC2822 and RFC850
 * @return GMTDate parsed from the string
 * @throws IllegalStateException if date format is not recognized
 */
fun String.fromHttpToGmtDate(): GMTDate

Supported HTTP Date Formats:

  • EEE, dd MMM yyyy HH:mm:ss zzz (RFC2822 format, preferred)
  • EEEE, dd-MMM-yyyy HH:mm:ss zzz (RFC850 format)
  • EEE MMM d HH:mm:ss yyyy (ANSI C asctime() format)
  • Various other legacy formats with different separators and orderings

Usage Examples:

import io.ktor.http.*
import io.ktor.util.date.*

// Parse standard HTTP dates
val rfc2822Date = "Tue, 15 Nov 1994 08:12:31 GMT".fromHttpToGmtDate()
println(rfc2822Date.year) // 1994
println(rfc2822Date.month.value) // "Nov"
println(rfc2822Date.dayOfMonth) // 15

// Parse RFC850 format
val rfc850Date = "Tuesday, 15-Nov-1994 08:12:31 GMT".fromHttpToGmtDate()
println(rfc850Date.hours) // 8
println(rfc850Date.minutes) // 12
println(rfc850Date.seconds) // 31

// Parse ANSI C format
val ansiDate = "Tue Nov 15 08:12:31 1994".fromHttpToGmtDate()
println(ansiDate.dayOfWeek.value) // "Tue"

// Handle various separator styles
val dashDate = "Tue, 15-Nov-1994 08:12:31 GMT".fromHttpToGmtDate()
val hyphenTime = "Tue, 15-Nov-1994 08-12-31 GMT".fromHttpToGmtDate()

// Error handling
try {
    val invalidDate = "Not a valid date".fromHttpToGmtDate()
} catch (e: IllegalStateException) {
    println("Failed to parse date: ${e.message}")
}

Cookie Date Parsing

Parse cookie date strings using RFC6265 format with fallback to HTTP date parsing.

/**
 * Parse cookie date string into GMTDate
 * First attempts RFC6265 cookie date format, falls back to HTTP date parsing
 * @return GMTDate parsed from the string
 * @throws IllegalStateException if date format is not recognized
 */
fun String.fromCookieToGmtDate(): GMTDate

Usage Examples:

import io.ktor.http.*
import io.ktor.util.date.*

// Parse RFC6265 cookie date format
val cookieDate = "Tue, 15 Nov 1994 08:12:31 GMT".fromCookieToGmtDate()
println(cookieDate.year) // 1994

// Cookie dates with different formats
val cookieDate2 = "Tuesday, 15-Nov-94 08:12:31 GMT".fromCookieToGmtDate()
val cookieDate3 = "Tue Nov 15 08:12:31 1994".fromCookieToGmtDate()

// Handles edge cases and whitespace
val trimmedDate = "  Tue, 15 Nov 1994 08:12:31 GMT  ".fromCookieToGmtDate()

// Falls back to HTTP date parsing for non-cookie formats
val httpFallback = "15 Nov 1994 08:12:31 GMT".fromCookieToGmtDate()

try {
    val malformedCookie = "Invalid cookie date".fromCookieToGmtDate()
} catch (e: IllegalStateException) {
    println("Could not parse cookie date: ${e.message}")
}

HTTP Date Formatting

Format GMT date objects into standard HTTP date strings.

/**
 * Format GMTDate as HTTP date string in RFC2822 format
 * @return HTTP date string in format "EEE, dd MMM yyyy HH:mm:ss GMT"
 */
fun GMTDate.toHttpDate(): String

Usage Examples:

import io.ktor.http.*
import io.ktor.util.date.*

// Create GMT date and format for HTTP headers
val gmtDate = GMTDate(
    year = 2023,
    month = Month.DECEMBER,
    dayOfMonth = 25,
    hours = 14,
    minutes = 30,
    seconds = 45
)

val httpDateString = gmtDate.toHttpDate()
println(httpDateString) // "Mon, 25 Dec 2023 14:30:45 GMT"

// Round-trip parsing and formatting
val originalDate = "Tue, 15 Nov 1994 08:12:31 GMT"
val parsedDate = originalDate.fromHttpToGmtDate()
val reformattedDate = parsedDate.toHttpDate()
println(reformattedDate) // "Tue, 15 Nov 1994 08:12:31 GMT"

// Format current time for HTTP headers
val now = GMTDate() // Current time
val httpNow = now.toHttpDate()
println("Current time: $httpNow")

// Zero-padded formatting
val earlyDate = GMTDate(
    year = 2001,
    month = Month.JANUARY,
    dayOfMonth = 1,
    hours = 0,
    minutes = 0,
    seconds = 0
)
val formatted = earlyDate.toHttpDate()
println(formatted) // "Mon, 01 Jan 2001 00:00:00 GMT"

Complete Date Handling Examples

import io.ktor.http.*
import io.ktor.util.date.*

// HTTP header date processing
fun processHttpDateHeaders(headers: Headers) {
    // Last-Modified header
    val lastModified = headers[HttpHeaders.LastModified]?.let { dateStr ->
        try {
            dateStr.fromHttpToGmtDate()
        } catch (e: IllegalStateException) {
            println("Invalid Last-Modified date: $dateStr")
            null
        }
    }
    
    // If-Modified-Since header
    val ifModifiedSince = headers[HttpHeaders.IfModifiedSince]?.let { dateStr ->
        try {
            dateStr.fromHttpToGmtDate()
        } catch (e: IllegalStateException) {
            println("Invalid If-Modified-Since date: $dateStr")
            null
        }
    }
    
    // Expires header
    val expires = headers[HttpHeaders.Expires]?.let { dateStr ->
        try {
            dateStr.fromHttpToGmtDate()
        } catch (e: IllegalStateException) {
            println("Invalid Expires date: $dateStr")
            null
        }
    }
    
    // Date comparison
    if (lastModified != null && ifModifiedSince != null) {
        val isModified = lastModified.timestamp > ifModifiedSince.timestamp
        println("Resource modified since request: $isModified")
    }
    
    // Expiration check
    if (expires != null) {
        val now = GMTDate()
        val isExpired = now.timestamp > expires.timestamp
        println("Resource expired: $isExpired")
    }
}

// Cookie expiration handling
fun processCookieExpiration(cookies: List<Cookie>) {
    val now = GMTDate()
    
    cookies.forEach { cookie ->
        val expiration = when {
            // Max-Age takes precedence over Expires
            cookie.maxAge > 0 -> {
                val futureTime = now.timestamp + (cookie.maxAge * 1000)
                GMTDate(futureTime)
            }
            // Use Expires date
            cookie.expires != null -> cookie.expires
            // Session cookie
            else -> null
        }
        
        when (expiration) {
            null -> println("Cookie '${cookie.name}' is a session cookie")
            else -> {
                val isExpired = now.timestamp > expiration.timestamp
                val timeLeft = (expiration.timestamp - now.timestamp) / 1000
                
                if (isExpired) {
                    println("Cookie '${cookie.name}' has expired")
                } else {
                    val hoursLeft = timeLeft / 3600
                    val daysLeft = hoursLeft / 24
                    
                    val timeDescription = when {
                        daysLeft > 1 -> "${daysLeft} days"
                        hoursLeft > 1 -> "${hoursLeft} hours"
                        else -> "${timeLeft} seconds"
                    }
                    
                    println("Cookie '${cookie.name}' expires in $timeDescription")
                }
            }
        }
    }
}

// HTTP caching utilities
object HttpCaching {
    fun isResourceFresh(
        lastModified: String?,
        etag: String?,
        ifModifiedSince: String?,
        ifNoneMatch: String?
    ): Boolean {
        // ETag matching takes precedence
        if (etag != null && ifNoneMatch != null) {
            return !matchesETag(etag, ifNoneMatch)
        }
        
        // Date-based validation
        if (lastModified != null && ifModifiedSince != null) {
            return try {
                val lastModDate = lastModified.fromHttpToGmtDate()
                val ifModDate = ifModifiedSince.fromHttpToGmtDate()
                lastModDate.timestamp > ifModDate.timestamp
            } catch (e: IllegalStateException) {
                true // Assume fresh if dates are invalid
            }
        }
        
        return true // Assume fresh if no validation info
    }
    
    private fun matchesETag(etag: String, ifNoneMatch: String): Boolean {
        val cleanETag = etag.removePrefix("W/").trim('"')
        val cleanIfNoneMatch = ifNoneMatch.removePrefix("W/").trim('"')
        return cleanETag == cleanIfNoneMatch || ifNoneMatch == "*"
    }
    
    fun generateCacheHeaders(lastModified: GMTDate, maxAge: Int): Map<String, String> {
        return mapOf(
            HttpHeaders.LastModified to lastModified.toHttpDate(),
            HttpHeaders.CacheControl to "public, max-age=$maxAge",
            HttpHeaders.Expires to GMTDate(lastModified.timestamp + maxAge * 1000).toHttpDate()
        )
    }
}

// Date validation and normalization
fun validateAndNormalizeDate(dateString: String, isForCookie: Boolean = false): String? {
    return try {
        val parsedDate = if (isForCookie) {
            dateString.fromCookieToGmtDate()
        } else {
            dateString.fromHttpToGmtDate()
        }
        parsedDate.toHttpDate()
    } catch (e: IllegalStateException) {
        null
    }
}

Types

/**
 * GMT date representation (from ktor-utils)
 */
class GMTDate {
    val year: Int
    val month: Month
    val dayOfMonth: Int
    val dayOfWeek: WeekDay
    val hours: Int
    val minutes: Int
    val seconds: Int
    val timestamp: Long
}

/**
 * Month enumeration
 */
enum class Month(val value: String) {
    JANUARY("Jan"), FEBRUARY("Feb"), MARCH("Mar"),
    APRIL("Apr"), MAY("May"), JUNE("Jun"),
    JULY("Jul"), AUGUST("Aug"), SEPTEMBER("Sep"),
    OCTOBER("Oct"), NOVEMBER("Nov"), DECEMBER("Dec")
}

/**
 * Week day enumeration
 */
enum class WeekDay(val value: String) {
    MONDAY("Mon"), TUESDAY("Tue"), WEDNESDAY("Wed"),
    THURSDAY("Thu"), FRIDAY("Fri"), SATURDAY("Sat"), SUNDAY("Sun")
}